package org.languagetool.dev.conversion.gui;

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.border.LineBorder;

import net.boplicity.xmleditor.XmlTextPane;

import org.languagetool.JLanguageTool;
import org.languagetool.dev.conversion.AtdRuleConverter;
import org.languagetool.dev.conversion.CgRuleConverter;
import org.languagetool.dev.conversion.RuleConverter;
import org.languagetool.dev.conversion.RuleCoverage;
import org.languagetool.rules.Rule;
import org.languagetool.rules.patterns.PatternRule;

public final class Main implements ActionListener {

	// Display elements
	private JFrame frame; // main frame

	private JComboBox rulesBox;
	private XmlTextPane resultArea;

	private JComboBox ruleTypeBox;
	private JComboBox specificRuleTypeBox;

	private JTextPane coveredByPane;
	private JTextPane warningPane;

	private JButton convert;
	private JButton saveEditedRule;
	private JButton deleteCurrentRule;
	private JButton toggleDefaultOff;
	private JButton makeRuleExclusive;
	private JButton toggleExtraTokens;
	private JButton checkRulesCoveredButton;
	private JButton recheckCurrentRuleCoverage;
	private JButton writeRulesToFileButton;

	private JCheckBox regularRules;
	private JCheckBox disambigRules;
	private JCheckBox noWarningRules;
	private JCheckBox warningRules;
	private JCheckBox coveredRules;
	private JCheckBox notCoveredRules;

	private JTextPane mainRuleFilePane;
	private JTextPane outFilePane;
	private JTextPane disambigOutFilePane;

	private JCheckBox writeCoveredRules;
	private JCheckBox editBeforeWriting;

	private JTextPane numRulesPane;
	private JTextPane displayedNumRulesPane;

	// Lists containing the rules
	private List<? extends Object> ruleObjects;
	private ArrayList<List<String>> allRulesList;
	private ArrayList<String> ruleStrings;
	private ArrayList<String> originalRuleStrings;
	private ArrayList<String[]> warnings;
	private boolean[] disambigRuleIndices;
	private ArrayList<String[]> coveredByList; // only applies to regular LT rules

	// Rule coverage
	private RuleCoverage checker;

	private String filename = "";
	private String outfilename = "";
	private String disambigOutFile = "";
	
	private boolean defaultOff = false;
	private boolean extraTokens = true;

	private int numberOfRules;

	// constants
	private static String cgString = "Constraint Grammar";
	private static String atdString = "After the Deadline";

	private static final int WINDOW_WIDTH = 850;
	private static final int WINDOW_HEIGHT = 800;

	private static Pattern ruleHeaderRegex = Pattern.compile("<rule(group)?\\s+id=\".*?\"\\s+name=\".*?\"");
	private static Pattern defaultOffRegex = Pattern.compile(" default=\"off\"");
	private static String defaultOffString = " default=\"off\"";
	private static Pattern postagToken = Pattern.compile("(<token postag=\"(.*?)\"( postag_regexp=\"yes\")?>)(</token>)");

	// main method
	public static void main(final String[] args) {
		try {
			final Main prg = new Main();
			if (args.length > 0) {
				System.out.println("Usage: java -jar RuleConverterGUI.jar");
				System.out.println("  no arguments");
			} else {
				javax.swing.SwingUtilities.invokeLater(new Runnable() {
					@Override
					public void run() {
						try {
							prg.createGUI();
							prg.showGUI();
						} catch (final Exception e) {
							showError(e);
						}
					}
				});
			}
		} catch (final Exception e) {
			showError(e);
		}
	}

	private void createGUI() {
		// main frame
		frame = new JFrame("Language Tool Rule Converter");
		frame.addWindowListener(new CloseListener());
		frame.setJMenuBar(new MainMenuBar(this));
		setLookAndFeel();

		// converted rule area
		resultArea = new XmlTextPane();
		resultArea.requestFocusInWindow();
		JScrollPane scrollPane = new JScrollPane(resultArea);
		scrollPane.setPreferredSize(new Dimension(250, 145));
		scrollPane.setMinimumSize(new Dimension(10, 10));

		// original rule combo box
		rulesBox = new JComboBox();
		rulesBox.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				displaySelectedRule();
				displayCoveredBy();
				displayWarnings();
			}
		});

		// rule type combo box
		ruleTypeBox = new JComboBox();
		ruleTypeBox.addItem(atdString);
		ruleTypeBox.addItem(cgString);
		ruleTypeBox.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				populateSpecificRuleType();
			}
		});
		// specific rule type
		specificRuleTypeBox = new JComboBox();
		populateSpecificRuleType();

		// rule file pane
		mainRuleFilePane = new JTextPane();
		mainRuleFilePane.setText(filename);
		mainRuleFilePane.setBorder(new LineBorder(Color.BLACK, 1));

		outFilePane = new JTextPane();
		outFilePane.setText(outfilename);
		outFilePane.setBorder(new LineBorder(Color.BLACK, 1));

		disambigOutFilePane = new JTextPane();
		disambigOutFilePane.setText(disambigOutFile);
		disambigOutFilePane.setBorder(new LineBorder(Color.BLACK, 1));

		// convert button
		convert = new JButton("Convert");
		convert.setMnemonic('C');
		convert.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				convertRuleFile();
				populateRuleBox();
			}
		});

		deleteCurrentRule = new JButton("Delete current rule");
		deleteCurrentRule.setMnemonic('D');
		deleteCurrentRule.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				deleteCurrentRule();
			}
		});

		toggleDefaultOff = new JButton("Toggle default=\"off\"");
		toggleDefaultOff.setMnemonic('t');
		toggleDefaultOff.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (!defaultOff) {
					setDefaultOff();
					defaultOff = true;
				} else {
					setDefaultOn();
					defaultOff = false;
				}
			}
		});
		
		
		makeRuleExclusive = new JButton("Make rule exclusive");
		makeRuleExclusive.setMnemonic('x');
		makeRuleExclusive.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				makeCurrentRuleExclusive();
			}
		});
		
		toggleExtraTokens = new JButton("Toggle extra tokens");
		toggleExtraTokens.setMnemonic('a');
		toggleExtraTokens.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				if (!extraTokens) {
					addExtraTokens();
					extraTokens = true;
				} else {
					removeExtraTokens();
					extraTokens = false;
				}
				
			}
		});

		// save rule button
		saveEditedRule = new JButton("Save rule");
		saveEditedRule.setMnemonic('S');
		saveEditedRule.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				saveEditedVisibleRule();
			}
		});

		// save all rules
		writeRulesToFileButton = new JButton("Write rules to file");
		writeRulesToFileButton.setMnemonic('W');
		writeRulesToFileButton.addActionListener(this);

		// covered by existing rule pane
		coveredByPane = new JTextPane();
		coveredByPane.setBorder(new LineBorder(Color.BLACK, 1));

		// warnings pane
		warningPane = new JTextPane();
		warningPane.setBorder(new LineBorder(Color.BLACK, 1));

		// put coveredByPane and warningPane together in one JPanel
		JPanel coveredWarningPanel = new JPanel(new GridBagLayout());
		GridBagConstraints cwcons = new GridBagConstraints();
		cwcons.weightx = 1f;
		cwcons.fill = GridBagConstraints.BOTH;
		cwcons.anchor = GridBagConstraints.WEST;
		coveredWarningPanel.add(new JLabel("Covered by:"), cwcons);
		cwcons.gridx = 1;
		coveredWarningPanel.add(new JLabel("Warnings:"), cwcons);
		cwcons.gridy = 1;
		coveredWarningPanel.add(warningPane, cwcons);
		cwcons.gridx = 0;
		coveredWarningPanel.add(coveredByPane, cwcons);

		// display regular and/or disambiguation rules check box
		regularRules = new JCheckBox("Show regular rules", true);
		disambigRules = new JCheckBox("Show disambiguation rules", true);
		warningRules = new JCheckBox("Show rules with warnings", true);
		noWarningRules = new JCheckBox("Show rules without warnings", true);
		coveredRules = new JCheckBox("Show covered rules", true);
		notCoveredRules = new JCheckBox("Show not covered rules", true);
		regularRules.addActionListener(this);
		disambigRules.addActionListener(this);
		warningRules.addActionListener(this);
		noWarningRules.addActionListener(this);
		coveredRules.addActionListener(this);
		notCoveredRules.addActionListener(this);

		// write covered rules to file check box
		writeCoveredRules = new JCheckBox("Write duplicate rules to file");
		editBeforeWriting = new JCheckBox("Edit rules before writing");

		// check if all current rules are covered button
		checkRulesCoveredButton = new JButton("Check rule coverage");
		checkRulesCoveredButton.setMnemonic('E');
		checkRulesCoveredButton.addActionListener(this);

		recheckCurrentRuleCoverage = new JButton(
				"Check displayed rule coverage");
		recheckCurrentRuleCoverage.setMnemonic('R');
		recheckCurrentRuleCoverage.addActionListener(this);

		// number of rules display
		final JLabel numRulesLabel = new JLabel("Total number of rules:");
		numRulesPane = new JTextPane();
		final JLabel numDisplayedRulesLabel = new JLabel(
				"Number of displayed rules:");
		displayedNumRulesPane = new JTextPane();

		// add everything into the frame
		final Container contentPane = frame.getContentPane();
		final GridBagLayout gridLayout = new GridBagLayout();
		contentPane.setLayout(gridLayout);
		final GridBagConstraints cons = new GridBagConstraints();
		cons.fill = GridBagConstraints.BOTH;

		// inside panel to hold all the buttons
		final GridBagConstraints buttonCons = new GridBagConstraints();
		final JPanel insidePanel = new JPanel();
		insidePanel.setOpaque(true);
		insidePanel.setLayout(new GridBagLayout());

		insidePanel.setBorder(new LineBorder(Color.BLACK, 1));
		buttonCons.fill = GridBagConstraints.BOTH;

		// buttonCons.weightx = 1f;

		// rule type
		JPanel ruleTypePanel = new JPanel(new GridBagLayout());
		GridBagConstraints c = new GridBagConstraints();
		ruleTypePanel.add(new JLabel("Rule type:"), c);
		c.gridx = 1;
		ruleTypePanel.add(ruleTypeBox, c);
		c.gridx = 2;
		ruleTypePanel.add(specificRuleTypeBox, c);

		insidePanel.add(ruleTypePanel, buttonCons);

		JPanel displayRulesPanel = new JPanel(new GridBagLayout());
		c.anchor = GridBagConstraints.WEST;
		c.gridx = 0;
		displayRulesPanel.add(regularRules, c);
		c.gridx = 1;
		displayRulesPanel.add(disambigRules, c);
		c.gridx = 0;
		c.gridy = 1;
		displayRulesPanel.add(warningRules, c);
		c.gridx = 1;
		displayRulesPanel.add(noWarningRules, c);
		c.gridx = 0;
		c.gridy = 2;
		displayRulesPanel.add(coveredRules, c);
		c.gridx = 1;
		displayRulesPanel.add(notCoveredRules, c);
		c.anchor = GridBagConstraints.CENTER;

		buttonCons.gridy = 1;
		insidePanel.add(displayRulesPanel, buttonCons);

		JPanel coveragePanel = new JPanel(new GridBagLayout());
		c.gridx = 0;
		coveragePanel.add(checkRulesCoveredButton, c);
		c.gridx = 1;
		coveragePanel.add(recheckCurrentRuleCoverage, c);

		buttonCons.gridy = 2;
		buttonCons.fill = GridBagConstraints.NONE;
		insidePanel.add(coveragePanel, buttonCons);
		

		JPanel modifyRulesPanel = new JPanel(new GridBagLayout());
		c.gridx = 0;
		modifyRulesPanel.add(toggleDefaultOff, c);
		c.gridx = 1;
		modifyRulesPanel.add(makeRuleExclusive, c);
		c.gridx = 2;
		modifyRulesPanel.add(toggleExtraTokens, c);
		buttonCons.gridy = 3;
		insidePanel.add(modifyRulesPanel, buttonCons);

		
		JPanel savePanel = new JPanel(new GridBagLayout());
		c.gridx = 0;
		savePanel.add(saveEditedRule, c);
		c.gridx = 1;
		savePanel.add(deleteCurrentRule, c);
		buttonCons.gridy = 4;
		insidePanel.add(savePanel, buttonCons);

		JPanel convertPanel = new JPanel(new GridBagLayout());
		c.gridx = 0;
		convertPanel.add(convert,c);
		c.gridx = 1;
		convertPanel.add(writeRulesToFileButton, c);
		buttonCons.gridy = 5;
		insidePanel.add(convertPanel, buttonCons);

		
		JPanel writeOptionsPanel = new JPanel(new GridBagLayout());
		c.gridx = 0;
		writeOptionsPanel.add(writeCoveredRules, c);
		c.gridx = 1;
		writeOptionsPanel.add(editBeforeWriting, c);
		buttonCons.gridy = 6;
		insidePanel.add(writeOptionsPanel, buttonCons);

		JPanel numRulesPanel = new JPanel(new GridBagLayout());
		c.gridx = 0;
		numRulesPanel.add(numRulesLabel, c);
		c.gridx = 1;
		numRulesPanel.add(numRulesPane, c);
		c.gridx = 2;
		numRulesPanel.add(numDisplayedRulesLabel, c);
		c.gridx = 3;
		numRulesPanel.add(displayedNumRulesPane, c);

		buttonCons.gridy = 7;
		insidePanel.add(numRulesPanel, buttonCons);

		cons.gridx = 0;
		cons.gridy = 0;
		cons.ipadx = 1;
		cons.ipady = 1;
		JLabel ruleLabel = new JLabel("Original rule:");
		contentPane.add(ruleLabel, cons);
		cons.gridx = 0;
		cons.gridy = 2;
		JLabel convertedRuleLabel = new JLabel("Converted rule:");
		contentPane.add(convertedRuleLabel, cons);
		cons.gridx = 0;
		cons.gridy = 1;
		cons.weightx = 10f;
		cons.weighty = 2f;
		contentPane.add(rulesBox, cons);
		cons.gridx = 0;
		cons.gridy = 3;
		cons.weightx = 10f;
		cons.weighty = 10f;
		cons.ipady = 150;
		scrollPane.setMinimumSize(new Dimension(0, 200));
		contentPane.add(scrollPane, cons);
		cons.gridx = 0;
		cons.gridy = 4;
		cons.ipady = 0;
		cons.ipadx = 0;
		cons.weightx = 0;
		cons.weighty = 0;
		cons.anchor = GridBagConstraints.WEST;
		contentPane.add(coveredWarningPanel, cons);
		cons.gridy = 5;
		contentPane.add(insidePanel, cons);
		cons.gridx = 0;
		cons.gridy = 6;
		contentPane.add(new JLabel("Rule file:"), cons);
		cons.gridy = 7;
		contentPane.add(mainRuleFilePane, cons);
		cons.gridy = 8;
		contentPane.add(new JLabel("Out file:"), cons);
		cons.gridy = 9;
		contentPane.add(outFilePane, cons);
		cons.gridy = 10;
		contentPane.add(new JLabel("Disambiguation out file:"), cons);
		cons.gridy = 11;
		contentPane.add(disambigOutFilePane, cons);

		frame.pack();
		frame.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
	}

	private void setLookAndFeel() {
		try {
			for (UIManager.LookAndFeelInfo info : UIManager
					.getInstalledLookAndFeels()) {
				if ("Nimbus".equals(info.getName())) {
					UIManager.setLookAndFeel(info.getClassName());
					break;
				}
			}
		} catch (Exception ignored) {
			// Well, what can we do...
		}
	}

	private void showGUI() {
		frame.setVisible(true);
	}

	void quit() {
		frame.setVisible(false);
		System.exit(0);
	}

	// Display methods

	private void displaySelectedRule() {
		if (rulesBox.getSelectedIndex() == -1) {
			resultArea.setText("");
		} else {
			String selectedRule = (String) rulesBox.getSelectedItem();
			for (int i = 0; i < originalRuleStrings.size(); i++) {
				if (selectedRule.equals(originalRuleStrings.get(i))) {
					resultArea.setText(ruleStrings.get(i));
					break;
				}
			}
			resultArea.repaint();
		}
	}

	private void displayCoveredBy() {
		if (rulesBox.getSelectedIndex() == -1) {
			coveredByPane.setText("");
		} else {
			if (coveredByList != null) {
				int index = getCurrentRuleIndex();
				String[] cov = coveredByList.get(index);
				StringBuilder sb = new StringBuilder();
				for (String s : cov) {
					sb.append(s + ", ");
				}
				coveredByPane.setText(sb.toString().trim());
			}
		}
	}

	public void displayCoveringRules() {
		String[] ruleIds = coveredByPane.getText().split(",\\ ?");
		openExistingRuleWindow(ruleIds);
	}

	private void displayWarnings() {
		if (rulesBox.getSelectedIndex() == -1) {
			warningPane.setText("");
		} else {
			if (warnings != null) {
				int index = getCurrentRuleIndex();
				warningPane.setText(listToString(warnings.get(index)));
				warningPane.repaint();
			}
		}
	}

	// displays the existing LT rule that covers the converted rule
	private void openExistingRuleWindow(String[] ruleIds) {
		if (checker == null) {
			return;
		}
		JLanguageTool tool = checker.getLanguageTool();
		String fetchedRuleString = "<pre>";
		List<Rule> rules = tool.getAllRules();
		for (String ruleId : ruleIds) {
			for (Rule rule : rules) {
				if (rule.getId().equals(ruleId)) {
					// can only display pattern rules
					try {
						PatternRule patternRule = (PatternRule) rule;
						String tempRuleString = patternRule.toXML();
						tempRuleString = tempRuleString.replaceAll("\\<",
								"&lt;").replaceAll("\\>", "&gt;");
						fetchedRuleString = fetchedRuleString.concat(
								tempRuleString).concat("<br>");
						break;
					} catch (ClassCastException e) {
						fetchedRuleString += "Can't display Java rules";
						break;
					}
				}
			}
		}
		fetchedRuleString = fetchedRuleString.concat("</pre>");
		showDialog("<html>" + fetchedRuleString + "</html>", "Existing LT Rule");
	}

	// generates outfile names from the input file name
	private void tieOutFileNames() {
		filename = getCurrentFilename();
		outfilename = filename + ".grammar.xml";
		disambigOutFile = filename + ".disambig.xml";
		outFilePane.setText(outfilename);
		disambigOutFilePane.setText(disambigOutFile);
	}

	// removes the currently displayed rule; it won't be written to the outfile
	public void deleteCurrentRule() {
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return;
		}
		int index = getCurrentRuleIndex();
		
		ruleObjects.remove(index);
		allRulesList.remove(index);
		ruleStrings.remove(index);
		originalRuleStrings.remove(index);
		warnings.remove(index);
		// removing from the disambigRuleIndices array
		disambigRuleIndices = removeIndexFromBooleanArray(disambigRuleIndices,
				index);
		coveredByList.remove(index);
		numberOfRules--;
		numRulesPane.setText(Integer.toString(numberOfRules));
		
		populateRuleBox();
		if (index > 0) {
			rulesBox.setSelectedItem(originalRuleStrings.get(index - 1));
		}
		
	}

	private static boolean[] removeIndexFromBooleanArray(boolean[] array,
			int index) {
		boolean[] n = new boolean[array.length - 1];
		for (int i = 0; i < index; i++) {
			n[i] = array[i];
		}
		for (int i = index + 1; i < array.length; i++) {
			n[i - 1] = array[i];
		}
		return n;
	}

	// some regex stuff to make the pos tags of the current rule exclusive (or "careful")
	public void makeCurrentRuleExclusive() {
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return;
		}
		int index = getCurrentRuleIndex();
		String rs = ruleStrings.get(index);
		rs = makeTokensExclusive(rs);
		ruleStrings.set(index,rs);
		displaySelectedRule();
	}
	
	public void makeAllRulesExclusive() {
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return;
		}
		for (int i=0;i<ruleStrings.size();i++) {
			ruleStrings.set(i,makeTokensExclusive(ruleStrings.get(i)));
		}
		populateRuleBox();
	}
	
	
	private String makeTokensExclusive(String ruleString) {
		Matcher m = postagToken.matcher(ruleString);
		while (m.find()) {
			String r = generateExclusiveException(m.group(2),m.group(3) != null);
			ruleString = ruleString.replace(m.group(),m.group(1) + r + m.group(4));
		}
		return ruleString;
	}
	
	private String generateExclusiveException(String postags, boolean regexp) {
		String ex = "<exception postag=\"";
        ex = ex.concat(postags).concat("\"");
        if (regexp) {
            ex = ex.concat(" postag_regexp=\"yes\" negate_pos=\"yes\"/>");
        } else {
            ex = ex.concat(" negate_pos=\"yes\"/>");
        }
        if (postags.equals(getCurrentRuleConverter().getSentStart()) || 
        	postags.equals(getCurrentRuleConverter().getSentEnd())) {
        	return "";
        }
        return ex;
	}
	
	// method to add the empty tokens at the beginning and end of the pattern, to catch extra overlapping rules
	
	// adds a default="off" attribute to the <rule> and <rulegroup> elements
	private void setDefaultOff() {
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return;
		}
		for (int i = 0; i < ruleStrings.size(); i++) {
			String oldRuleString = ruleStrings.get(i);
			String newRuleString = addOffAttribute(oldRuleString);
			ruleStrings.set(i, newRuleString);
		}
		displaySelectedRule();
	}

	private void setDefaultOn() {
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return;
		}
		for (int i = 0; i < ruleStrings.size(); i++) {
			String oldRuleString = ruleStrings.get(i);
			String newRuleString = removeOffAttribute(oldRuleString);
			ruleStrings.set(i, newRuleString);
		}
		displaySelectedRule();
	}

	private String removeOffAttribute(String rule) {
		Matcher alreadyOff = defaultOffRegex.matcher(rule);
		if (alreadyOff.find()) {
			String newRule = rule.replace(alreadyOff.group(), "");
			return newRule;
		} else {
			return rule;
		}
	}

	private String addOffAttribute(String rule) {
		Matcher alreadyOff = defaultOffRegex.matcher(rule);
		if (alreadyOff.find()) {
			return rule;
		} else {
			Matcher ruleHeader = ruleHeaderRegex.matcher(rule);
			if (!ruleHeader.find()) {
				System.out.println();
			}

			String newRule = rule.replace(ruleHeader.group(), ruleHeader
					.group().concat(defaultOffString));
			return newRule;
		}
	}

	// the main workhorse
	private void convertRuleFile() {
		RuleConverter rc = getCurrentRuleConverter();
		this.rulesBox.removeAllItems();
		try {
			// the generic rule objects, just used for building the other lists
			rc.parseRuleFile();
			ruleObjects = rc.getRules();
			// lists of strings
			allRulesList = rc.getAllLtRules();
			// rules in LT format, as strings
			ruleStrings = new ArrayList<String>();
			// original rule strings
			originalRuleStrings = rc.getOriginalRuleStrings();
			// warnings
			warnings = rc.getWarnings();
			// populating the string lists
			for (int i = 0; i < allRulesList.size(); i++) {
				List<String> ruleList = allRulesList.get(i);
				String ruleString = RuleConverter
						.getRuleStringFromList(ruleList);
				ruleStrings.add(ruleString);
			}
			// take out exact copies of rules (this messes up navigating through
			// the rules with the arrow keys)
			removeDuplicateRules();

			disambigRuleIndices = new boolean[allRulesList.size()];
			// list of existing LT rules that cover the new rules
			coveredByList = new ArrayList<String[]>();
			numberOfRules = allRulesList.size();
			numRulesPane.setText(Integer.toString(numberOfRules));

			for (int i = 0; i < ruleObjects.size(); i++) {
				Object ruleObject = ruleObjects.get(i);
				if (rc.isDisambiguationRule(ruleObject)) {
					disambigRuleIndices[i] = true;
				} else {
					disambigRuleIndices[i] = false;
				}
				coveredByList.add(new String[0]);
			}

			tieOutFileNames();

		} catch (IOException e) {
			showDialog("IOException while loading/parsing file " + filename,
					null);
		}
	}

	private void removeDuplicateRules() {
		boolean notdone = true;
		while (notdone) {
			for (int i = 0; i < originalRuleStrings.size(); i++) {
				String originalRuleString = originalRuleStrings.get(i);
				if (originalRuleStrings.subList(i + 1,
						originalRuleStrings.size())
						.contains(originalRuleString)) {
					originalRuleStrings.remove(i);
					ruleStrings.remove(i);
					allRulesList.remove(i);
					ruleObjects.remove(i);
					warnings.remove(i);
					break;
				} else {
					if (i == originalRuleStrings.size() - 1)
						notdone = false;
				}
			}
		}
	}

	private void populateRuleBox() {
		rulesBox.removeAllItems();
		boolean showRegularRules = regularRules.isSelected();
		boolean showDisambigRules = disambigRules.isSelected();
		boolean showWarningRules = warningRules.isSelected();
		boolean showNoWarningRules = noWarningRules.isSelected();
		boolean showCoveredRules = coveredRules.isSelected();
		boolean showNotCoveredRules = notCoveredRules.isSelected();
		int numDisplayed = 0;
		if (originalRuleStrings != null) {
			for (int i = 0; i < originalRuleStrings.size(); i++) {
				boolean cov = coveredByList.get(i).length != 0;
				boolean war = !(warnings.get(i).length == 0);
				boolean dis = disambigRuleIndices[i];
				if (dis && cov && war) {
					if (showDisambigRules && showCoveredRules
							&& showWarningRules) {
						rulesBox.addItem(originalRuleStrings.get(i));
						numDisplayed++;
					}
				} else if (dis && cov && !war) {
					if (showDisambigRules && showCoveredRules
							&& showNoWarningRules) {
						rulesBox.addItem(originalRuleStrings.get(i));
						numDisplayed++;
					}
				} else if (dis && !cov && war) {
					if (showDisambigRules && showNotCoveredRules
							&& showWarningRules) {
						rulesBox.addItem(originalRuleStrings.get(i));
						numDisplayed++;
					}
				} else if (dis && !cov && !war) {
					if (showDisambigRules && showNotCoveredRules
							&& showNoWarningRules) {
						rulesBox.addItem(originalRuleStrings.get(i));
						numDisplayed++;
					}
				} else if (!dis && cov && war) {
					if (showRegularRules && showCoveredRules
							&& showWarningRules) {
						rulesBox.addItem(originalRuleStrings.get(i));
						numDisplayed++;
					}
				} else if (!dis && cov && !war) {
					if (showRegularRules && showCoveredRules
							&& showNoWarningRules) {
						rulesBox.addItem(originalRuleStrings.get(i));
						numDisplayed++;
					}
				} else if (!dis && !cov && war) {
					if (showRegularRules && showNotCoveredRules
							&& showWarningRules) {
						rulesBox.addItem(originalRuleStrings.get(i));
						numDisplayed++;
					}
				} else if (!dis && !cov && !war) {
					if (showRegularRules && showNotCoveredRules
							&& showNoWarningRules) {
						rulesBox.addItem(originalRuleStrings.get(i));
						numDisplayed++;
					}
				}
			}
			displayedNumRulesPane.setText(Integer.toString(numDisplayed));
		}
	}

	private void populateSpecificRuleType() {
		String[] ft = getCurrentRuleConverter().getAcceptableFileTypes();
		specificRuleTypeBox.removeAllItems();
		for (String s : ft) {
			specificRuleTypeBox.addItem(s);
		}
	}

	// Methods to get properties of the gui object

	private RuleConverter getCurrentRuleConverter() {
		RuleConverter rc = null;
		String type = (String) ruleTypeBox.getSelectedItem();
		String specificType = (String) specificRuleTypeBox.getSelectedItem();
		if (specificType == null) {
			specificType = "default";
		}

		if (type.equals(atdString)) {
			rc = new AtdRuleConverter(getCurrentFilename(), null, specificType);
		} else if (type.equals(cgString)) {
			rc = new CgRuleConverter(getCurrentFilename(), null, specificType);
		}
		return rc;
	}

	private String getCurrentFilename() {
		try {
			String fn = mainRuleFilePane.getText();
			filename = fn;
			return mainRuleFilePane.getText();
		} catch (NullPointerException e) {
			return "";
		}
	}

	private int getCurrentRuleIndex() {
		String selectedRule = (String) rulesBox.getSelectedItem();
		int index;
		for (index = 0; index < originalRuleStrings.size(); index++) {
			if (selectedRule.equals(originalRuleStrings.get(index))) {
				break;
			}
		}
		return index;
	}

	private String getCurrentOutfile() {
		if (outfilename.equals("")) {
			outfilename = filename + ".grammar.xml";
			return outfilename;
		} else {
			if (!outFilePane.getText().equals(outfilename)) {
				outfilename = outFilePane.getText();
			}
			return outfilename;
		}
	}

	private String getCurrentDisambigFile() {
		if (disambigOutFile.equals("")) {
			disambigOutFile = filename + ".disambig.xml";
			return disambigOutFile;
		} else {
			if (!disambigOutFilePane.getText().equals(disambigOutFile)) {
				disambigOutFile = disambigOutFilePane.getText();
			}
			return disambigOutFile;
		}
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == checkRulesCoveredButton) {
			checkIfAllCurrentRulesCovered();
		} else if (e.getSource() == writeRulesToFileButton) {
			try {
				writeRulesToFile();
			} catch (IOException ex) {
				showError(ex);
			}
		} else if (e.getSource() == recheckCurrentRuleCoverage) {
			checkDisplayedRuleCoverage();
		} else if (e.getSource() == disambigRules
				|| e.getSource() == regularRules
				|| e.getSource() == warningRules
				|| e.getSource() == noWarningRules
				|| e.getSource() == coveredRules
				|| e.getSource() == notCoveredRules) {
			populateRuleBox();
		}
	}

	// checks if the currently displayed rule is covered by an existing language
	// tool rule
	public void checkDisplayedRuleCoverage() {
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return;
		}
		try {
			if (checker == null) {
				checker = new RuleCoverage();
				// checker = new
				// RuleCoverage("/home/mbryant/languagetool/JLanguageTool/src/resource/en/english.dict");
			}
			// if the currently displayed rule hasn't been saved yet
			int index = getCurrentRuleIndex();
			if (!ruleStrings.get(index).equals(resultArea.getText())) {
				showDialog("Current rule not yet saved", null);
			} else {
				List<PatternRule> patternRules = checker
						.parsePatternRule(resultArea.getText());
				ArrayList<String[]> allCoveringRules = checker
						.isCoveredBy(patternRules);
				ArrayList<String> coveringRules = new ArrayList<String>();
				for (String[] s : allCoveringRules) {
					for (String ss : s) {
						coveringRules.add(ss);
					}
				}
				coveredByList.set(index, coveringRules
						.toArray(new String[coveringRules.size()]));
				displayCoveredBy();
			}
		} catch (IOException e) {
			showDialog(
					"Couldn't parse or check the rule's coverage for some reason",
					null);
		}
	}
	
	private void addExtraTokens() {
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return;
		}
		int index = getCurrentRuleIndex();
		String rs = ruleStrings.get(index);
		rs = rs.replace("<pattern>\n","<pattern>\n<token/>\n");
		rs = rs.replace("</pattern>\n","<token/>\n</pattern>\n");
		ruleStrings.set(index,rs);
		displaySelectedRule();
	}
	
	private void removeExtraTokens() {
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return;
		}
		int index = getCurrentRuleIndex();
		String rs = ruleStrings.get(index);
		rs = rs.replace("<pattern>\n<token/>\n","<pattern>\n");
		rs = rs.replace("<token/>\n</pattern>\n","</pattern>\n");
		ruleStrings.set(index,rs);
		displaySelectedRule();
	}
	
	

	// checks if all the loaded rules (individually) are covered by existing LT
	// rules
	public void checkIfAllCurrentRulesCovered() {
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return;
		}
		try {
			checker = new RuleCoverage();
			// checker = new
			// RuleCoverage("/home/mbryant/languagetool/JLanguageTool/src/resource/en/english.dict");
			for (int i = 0; i < ruleStrings.size(); i++) {
				if (disambigRuleIndices[i]) {
					continue; // don't check disambiguation (or immunized) rules
				}
				List<PatternRule> patternRules = checker
						.parsePatternRule(ruleStrings.get(i));
				ArrayList<String[]> allCoveringRules = checker
						.isCoveredBy(patternRules);
				ArrayList<String> coveringRules = new ArrayList<String>();
				for (String[] s : allCoveringRules) {
					for (String ss : s) {
						coveringRules.add(ss);
					}
				}
				coveredByList.set(i, coveringRules
						.toArray(new String[coveringRules.size()]));
				displayCoveredBy();
			}
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	// saves the changes made in the result area to the current rule.
	// modifies what gets written to file
	private void saveEditedVisibleRule() {
		if (originalRuleStrings != null && ruleStrings != null && rulesBox.getSelectedItem() != null) {
			String newRule = resultArea.getText();
			String selectedRule = (String) rulesBox.getSelectedItem();
			int index;
			for (index = 0; index < originalRuleStrings.size(); index++) {
				if (selectedRule.equals(originalRuleStrings.get(index))) {
					break;
				}
			}
			ruleStrings.set(index, newRule);
		}
	}
	
	public void clickSaveButton() {
		saveEditedRule.doClick();
	}
	
	public void removeCoveringRules() {
		int index = getCurrentRuleIndex();
		coveredByList.set(index, new String[0]);
		displayCoveredBy();
	}
	
	public void removeWarnings() {
		int index = getCurrentRuleIndex();
		warnings.set(index, new String[0]);
		displayWarnings();
	}

	public void writeRulesToFile() throws IOException {
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return;
		}
		boolean writeCovered = writeCoveredRules.isSelected();
		int numReg = 0;
		int numDis = 0;
		StringBuilder regWriteString = new StringBuilder();
		StringBuilder disWriteString = new StringBuilder();
		
		if (anyRegularRules()) {
			regWriteString.append("<category name=\"Auto-generated rules "
					+ new File(filename).getName() + "\">\n");
			for (int i = 0; i < ruleStrings.size(); i++) {
				if (!disambigRuleIndices[i]
						&& (writeCovered || (!writeCovered && coveredByList
								.get(i).length == 0))) {
					regWriteString.append(ruleStrings.get(i));
					numReg++;
				}
			}
			regWriteString.append("</category>");
		}
		if (anyDisambiguationRules()) {
			disWriteString.append("<category name=\"Auto-generated rules "
					+ new File(filename).getName() + "\">\n");
			for (int i = 0; i < ruleStrings.size(); i++) {
				if (disambigRuleIndices[i]) {
					disWriteString.append(ruleStrings.get(i));
					numDis++;
				}
			}
			disWriteString.append("</category>");
		}

		String disString = disWriteString.toString();
		String regString = regWriteString.toString();

		if (editBeforeWriting.isSelected()) {
			writeWithEditing(disString, regString, numDis, numReg);
		} else {
			writeWithoutEditing(disString, regString, numDis, numReg);
		}

	}

	// displays a TextPane that lets you edit the entire rule file before
	// writing it
	private void writeWithEditing(String dis, String reg, int numDis, int numReg)
			throws IOException {
		XmlDisplay regdisplay = new XmlDisplay(reg, getCurrentOutfile(),
				"Edit regular out file");
		regdisplay.show();

		XmlDisplay disdisplay = new XmlDisplay(dis, getCurrentDisambigFile(),
				"Edit disambiguation file");
		disdisplay.show();
	}

	// just writes the rules to file
	private void writeWithoutEditing(String dis, String reg, int numDis,
			int numReg) throws IOException {
		if (!reg.isEmpty()) {
			PrintWriter w = new PrintWriter(new OutputStreamWriter(
					new FileOutputStream(outfilename), "UTF-8"));
			w.write(reg);
			w.close();
		}
		if (!dis.isEmpty()) {
			PrintWriter w = new PrintWriter(new OutputStreamWriter(
					new FileOutputStream(disambigOutFile), "UTF-8"));
			w.write(dis);
			w.close();
		}

		String message = "";
		if (numReg > 0) {
			message += Integer.toString(numReg) + " rules written to "
					+ outfilename + "<br>";
		}
		if (numDis > 0) {
			message += Integer.toString(numDis) + " rules written to "
					+ disambigOutFile;
		}
		if (message.equals("")) {
			message = "No rules written";
		}
		showDialog(message, null);
	}

	// returns true if there are any regular rules
	private boolean anyRegularRules() {
		for (boolean b : disambigRuleIndices) {
			if (!b)
				return true;
		}
		return false;
	}

	// returns true if there any disambiguation rules
	private boolean anyDisambiguationRules() {
		for (boolean b : disambigRuleIndices) {
			if (b)
				return true;
		}
		return false;
	}

	// shows a dialog box with the specified text
	private void showDialog(String message, String title) {
		final JDialog writeDialog = new JDialog(this.frame);
		JLabel label = new JLabel("<html>" + message + "</html>");
		GridBagConstraints cons = new GridBagConstraints();
		cons.insets = new Insets(2, 2, 2, 2);
		cons.gridx = 0;
		cons.gridy = 0;
		cons.ipady = 10;
		cons.ipadx = 10;
		writeDialog.setLayout(new GridBagLayout());
		writeDialog.add(label, cons);
		if (title == null) {
			title = "Message";
		}
		writeDialog.setTitle(title);
		writeDialog.setLocation(300, 300);

		// close dialog window if ESC pressed
		final KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
		final ActionListener actionListener = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent actionEvent) {
				writeDialog.setVisible(false);
			}
		};
		final JRootPane rootPane = writeDialog.getRootPane();
		rootPane.registerKeyboardAction(actionListener, stroke,
				JComponent.WHEN_IN_FOCUSED_WINDOW);
		writeDialog.pack();
		writeDialog.setVisible(true);
	}

	// shows the load file directory dialog
	private String loadFile(Frame f, String title, String fileType) {
		String fn = getCurrentFilename();
		FileDialog fd = new FileDialog(f, title, FileDialog.LOAD);
		fd.setFile(fileType);
		String path = fn.replaceAll(new File(fn).getName(), ""); // set the default dir to the path of the current file
		fd.setDirectory(path);
		fd.setLocation(50, 50);
		fd.setVisible(true);
		return fd.getDirectory() + fd.getFile();
	}

	public void loadFile() {
		String fn = loadFile(frame, "Load grammar file", null);
		if (!fn.equals("nullnull")) {
			filename = fn;
			mainRuleFilePane.setText(filename);
			String fileString = readFileAsString(filename);
			if (fileString.contains("::")) {
				ruleTypeBox.setSelectedItem("After the Deadline");
			} else if (fileString.contains("REMOVE")
					|| fileString.contains("SELECT")
					|| fileString.contains("LIST")
					|| fileString.contains("SET")) {
				ruleTypeBox.setSelectedItem("Constraint Grammar");
			}
			mainRuleFilePane.setText(filename);
			mainRuleFilePane.repaint();
		}
	}

	// reads in the entire file, so we can check what kind of file it is
	private String readFileAsString(String filename) {
		String line = null;
		StringBuilder sb = new StringBuilder();
		try {
			BufferedReader reader = new BufferedReader(new FileReader(filename));
			while ((line = reader.readLine()) != null) {
				sb.append(line);
				sb.append('\n');
			}
		} catch (IOException e) {
			// do nothing if you can't get at the file for some reason
		}
		return sb.toString();
	}

	// doesn't let you edit the rules
	public void showAllRules() {
		final JDialog rulesDialog = new JDialog(this.frame);
		rulesDialog.setMinimumSize(new Dimension(500, 500));
		JTextPane rulesPane = new JTextPane();
		String allRules = getAllRules();
		rulesPane.setText(allRules);
		JScrollPane scrollPane = new JScrollPane(rulesPane);
		// close dialog window if ESC pressed
		final KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
		final ActionListener actionListener = new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent actionEvent) {
				rulesDialog.setVisible(false);
			}
		};
		final JRootPane rootPane = rulesDialog.getRootPane();
		rootPane.registerKeyboardAction(actionListener, stroke,
				JComponent.WHEN_IN_FOCUSED_WINDOW);
		rulesDialog.add(scrollPane);
		rulesDialog.setVisible(true);
	}

	private String getAllRules() {
		StringBuilder sb = new StringBuilder();
		if (ruleStrings == null || rulesBox.getSelectedItem() == null) {
			return "";
		}
		for (String r : ruleStrings) {
			sb.append(r);
		}
		return sb.toString();
	}
	
	public void showOriginalRuleFile() {
		if (filename == null) {
			return;
		}
		String f = readFileAsString(filename);
		XmlDisplay regdisplay = new XmlDisplay(f, filename, "Edit existing rules file");
		regdisplay.show();
		
	}

	public void displayAboutDialog() {
		showDialog(
				"RuleConverterGUI:<br>Tool for converting rule files from other modalities to LT format.<br>"
						+ "Currently supported: After the Deadline and Constraint Grammar\n",
				"About");
	}

	// navigate through the rules of the file
	public void nextRule() {
		if (rulesBox != null) {
			try {
				rulesBox.setSelectedIndex(rulesBox.getSelectedIndex() + 1);
			} catch (IndexOutOfBoundsException e) {
				// nothing
			} catch (IllegalArgumentException e) {
				// nothing
			}
		}
	}

	public void prevRule() {
		if (rulesBox != null) {
			try {
				rulesBox.setSelectedIndex(rulesBox.getSelectedIndex() - 1);
			} catch (IndexOutOfBoundsException e) {
				// nothing
			} catch (IllegalArgumentException e) {
				// nothing
			}
		}
	}

	// not really used very often
	public static void showError(final Exception e) {
		final String msg = org.languagetool.tools.Tools
				.getFullStackTrace(e);
		JOptionPane.showMessageDialog(null, msg, "Error",
				JOptionPane.ERROR_MESSAGE);
		e.printStackTrace();
	}

	private static String listToString(String[] list) {
		StringBuilder sb = new StringBuilder();
		for (String s : list) {
			sb.append(s);
			sb.append("\n");
		}
		return sb.toString();
	}

	public void cutSelectedText() {
		resultArea.cut();
	}
	
	public void copySelectedText() {
		resultArea.copy();
	}
	
	public void pasteText() {
		resultArea.paste();
	}
	

	/**
	 * Creates an XML display dialog to show rules with proper syntax highlighting
	 * @author mbryant
	 *
	 */
	class XmlDisplay implements ActionListener {

		private JFrame xmlframe;
		private XmlTextPane pane;
		private JScrollPane scrollpane;
		private JButton done;
		private JButton cancel;
		private String text;
		private String title;
		private String fn;

		public XmlDisplay(String text, String filename, String title) {
			this.text = text;
			this.fn = filename;
			this.title = title;
		}

		public boolean isVisible() {
			return this.xmlframe.isVisible();
		}

		public void show() {
			xmlframe = new JFrame(title);
			final Container contentPane = xmlframe.getContentPane();
			final GridBagLayout gridLayout = new GridBagLayout();
			contentPane.setLayout(gridLayout);
			GridBagConstraints c = new GridBagConstraints();
			c.fill = GridBagConstraints.BOTH;
			c.weightx = 1f;
			c.weighty = 1f;

			pane = new XmlTextPane();
			pane.setText(text);
			pane.requestFocusInWindow();
			scrollpane = new JScrollPane(pane);
			scrollpane
					.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);

			done = new JButton("Done");
			done.addActionListener(this);
			done.setMnemonic('D');
			cancel = new JButton("Cancel");
			cancel.addActionListener(this);
			cancel.setMnemonic('C');

			c.gridwidth = 2;
			contentPane.add(scrollpane, c);
			c.gridy = 1;
			c.weighty = 0;
			c.gridwidth = 1;
			c.fill = GridBagConstraints.NONE;
			contentPane.add(done, c);
			c.gridx = 1;
			contentPane.add(cancel, c);

			final JRootPane rootPane = xmlframe.getRootPane();
			final KeyStroke escStroke = KeyStroke.getKeyStroke(
					KeyEvent.VK_ESCAPE, 0);
			final ActionListener actionListener = new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					xmlframe.setVisible(false);
				}
			};
			rootPane.registerKeyboardAction(actionListener, escStroke,
					JComponent.WHEN_IN_FOCUSED_WINDOW);
			// press ctrl+enter to OK-exit
			final KeyStroke enterStroke = KeyStroke.getKeyStroke(
					KeyEvent.VK_ENTER, KeyEvent.CTRL_MASK);
			final ActionListener actionListener2 = new ActionListener() {
				@Override
				public void actionPerformed(ActionEvent e) {
					done.doClick();
				}
			};
			rootPane.registerKeyboardAction(actionListener2, enterStroke,
					JComponent.WHEN_IN_FOCUSED_WINDOW);

			xmlframe.pack();
			xmlframe.setSize(WINDOW_WIDTH, WINDOW_HEIGHT);
			xmlframe.setVisible(true);
		}

		public String getText() {
			return this.text;
		}

		private void write() {
			if (!this.text.isEmpty()) {
				try {
					PrintWriter w = new PrintWriter(new OutputStreamWriter(
							new FileOutputStream(fn), "UTF-8"));
					w.write(this.text);
					w.close();
					showDialog("Written to " + fn, null);
				} catch (IOException e) {
					showError(e);
				}
			}
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			if (e.getSource() == done) {
				this.text = pane.getText();
				this.write();
				xmlframe.setVisible(false);
			} else if (e.getSource() == cancel) {
				xmlframe.setVisible(false);
			}
		}
	}
	
	class CloseListener implements WindowListener {

		@Override
		public void windowClosing(WindowEvent e) {
			quit();
		}
		@Override
		public void windowActivated(WindowEvent e) {}
		@Override
		public void windowClosed(WindowEvent e) {}
		@Override
		public void windowDeactivated(WindowEvent e) {}
		@Override
		public void windowDeiconified(WindowEvent e) {}
		@Override
		public void windowIconified(WindowEvent e) {}
		@Override
		public void windowOpened(WindowEvent e) {}
	}

}
